package demo.util;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalAccessor;
import java.time.temporal.TemporalUnit;
import java.util.Date;
import java.util.concurrent.TimeUnit;

/**
 * 日期、时间 相关工具类
 *
 * @author Stan.H
 */
public class DateTimeUtils {

    private DateTimeUtils() {
        super();
    }

    /**
     * yyyy-MM-dd HH:mm:ss
     */
    public static final DateTimeFormatter Y_M_D_H_M_S = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    /**
     * yyyy-MM-dd H:mm:ss
     */
    public static final DateTimeFormatter Y_M_D_H_M_S_2 = DateTimeFormatter.ofPattern("yyyy-MM-dd H:mm:ss");

    /**
     * yyyyMMddHHmmss
     */
    public static final DateTimeFormatter Y_M_D_H_M_S_3 = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");

    /**
     * MM-dd HH:mm:ss
     */
    public static final DateTimeFormatter M_D_H_M_S = DateTimeFormatter.ofPattern("MM-dd HH:mm:ss");

    /**
     * MM-dd HH:mm
     */
    public static final DateTimeFormatter M_D_H_M = DateTimeFormatter.ofPattern("MM-dd HH:mm");

    /**
     * HH:mm
     */
    public static final DateTimeFormatter H_M = DateTimeFormatter.ofPattern("HH:mm");


    /**
     * HH:mm:ss
     */
    public static final DateTimeFormatter H_M_S = DateTimeFormatter.ofPattern("HH:mm:ss");

    /**
     * 格式化成时分秒，如： HH:mm:ss
     *
     * @param timestamp
     * @return
     */
    public static String formatHHmmss(long timestamp) {
        return H_M_S.format(toLocalDateTime(timestamp));
    }

    /**
     * 格式化成时分,如： HH:mm
     *
     * @param timestamp
     * @return
     */
    public static String formatHHmm(long timestamp) {
        return H_M.format(toLocalDateTime(timestamp));
    }

    /**
     * 格式化成 yyyy-MM-dd HH:mm:ss
     *
     * @param timestamp
     * @return
     */
    public static String formatYmdHms(long timestamp) {
        return Y_M_D_H_M_S.format(toLocalDateTime(timestamp));
    }


    /**
     * 格式化成 yyyyMMddHHmmss
     *
     * @param timestamp
     * @return
     */
    public static String yyyyMMddHHmmss3(long timestamp) {
        return Y_M_D_H_M_S_3.format(toLocalDateTime(timestamp));
    }

    /**
     * 格式化成 MM-dd HH:mm
     *
     * @param timestamp
     * @return
     */
    public static String formatMMddHHmm(long timestamp) {
        return M_D_H_M.format(toLocalDateTime(timestamp));
    }

    /**
     * 格式化成 MM-dd HH:mm:ss
     *
     * @param timestamp
     * @return
     */
    public static String formatMMddHHmmss(long timestamp) {
        return M_D_H_M_S.format(toLocalDateTime(timestamp));
    }

    /**
     * 时间戳转时间
     *
     * @param timestamp
     * @return
     */
    public static LocalDateTime toLocalDateTime(long timestamp) {
        Instant instant = Instant.ofEpochMilli(timestamp);
        OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.of("+8"));
        return offsetDateTime.toLocalDateTime();
    }

    /**
     * LocalDateTime 转Date
     *
     * @param localDateTime
     * @return
     */
    public static Date from(LocalDateTime localDateTime) {
        if (localDateTime == null) {
            return null;
        }
        Instant instant = localDateTime.toInstant(ZoneOffset.of("+8"));
        return Date.from(instant);
    }

    /**
     * @param text
     * @param formatter
     * @return
     */
    public static LocalDateTime parse(CharSequence text, DateTimeFormatter formatter) {
        if (formatter == null) {
            formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME;
        }
        return LocalDateTime.parse(text, formatter);
    }

    public static long parseTs(CharSequence text, DateTimeFormatter formatter) {
        return LocalDateTime
                .parse(text, formatter)
                .atZone(ZoneOffset.of("+8")) // 明确指定时区
                .toInstant()
                .toEpochMilli();
    }


    /**
     * 持续时长: 1天2小时3分钟
     *
     * @param start
     * @param end
     * @return
     */
    public static String formatDuration(long start, long end) {
        long seconds = (end - start) / 1000;
        long hours = seconds / 3600;
        long minutes = (seconds % 3600) / 60;
        long days = hours / 24;
        if (days > 0) {
            hours = hours % 24;
        }
        StringBuilder sb = new StringBuilder();
        if (days > 0) {
            sb.append(days).append("天");
        }
        if (hours > 0) {
            sb.append(hours).append("小时");
        }
        if (minutes > 0) {
            sb.append(minutes).append("分钟");
        }
        return sb.toString();
    }

    /**
     * Returns a copy of this {@code LocalDateTime} with the time truncated.
     * <p>
     * Truncation returns a copy of the original date-time with fields
     * smaller than the specified unit set to zero.
     * For example, truncating with the {@link ChronoUnit#MINUTES minutes} unit
     * will set the second-of-minute and nano-of-second field to zero.
     * <p>
     * The unit must have a {@linkplain TemporalUnit#getDuration() duration}
     * that divides into the length of a standard day without remainder.
     * This includes all supplied time units on {@link ChronoUnit} and
     * {@link ChronoUnit#DAYS DAYS}. Other units throw an exception.
     * <p>
     * This instance is immutable and unaffected by this method call.
     *
     * @param timestamp
     * @param unit      the unit to truncate to, not null
     * @return a {@code LocalDateTime} based on this date-time with the time truncated, not null
     */
    public static long truncatedTo(long timestamp, ChronoUnit unit) {
        LocalDateTime localDateTime = toLocalDateTime(timestamp).truncatedTo(unit);
        return localDateTime == null ? 0 : from(localDateTime).getTime();
    }


    /**
     * 判断当前是否在可用的时间区间
     * <li>如[21:00,09:00]表达的是晚9点到第二天的早9点
     * <li>如[09:00,21:00]表达的是早9点到晚9点
     *
     * @param start
     * @param end
     * @return
     */
    public static boolean isNowInTimeRange(LocalTime start, LocalTime end) {
        if (start == null || end == null) {
            return false;
        }
        LocalTime now = LocalTime.now();
        int compare = start.compareTo(end);
        // 如：09:00 < 21:00
        if (compare < 0) {
            return now.isAfter(start) && now.isBefore(end);
        } else if (compare > 0) {// 如 [21:00,09:00]，表达的是晚9点到第二天的早9点
            return (now.isAfter(start) && now.isBefore(LocalTime.MAX))
                    || (now.isAfter(LocalTime.MIN) && now.isBefore(end));
        } else {
            return now.equals(start);
        }
    }


    /**
     * parseUTC
     *
     * @param utcStr . UTC 时间格式，如 2023-03-08T00:00:00Z
     * @return
     */
    public static long parseUTC(String utcStr) {
        TemporalAccessor temporalAccessor = DateTimeFormatter.ISO_INSTANT.parse(utcStr);
        Instant instant = Instant.from(temporalAccessor);
        return instant.toEpochMilli();
    }


    /**
     * 将毫秒转化成 1h2m3s4ms 格式
     *
     * @param milliseconds
     * @return
     */
    public static String convertDuration(long milliseconds) {
        if (milliseconds < 1000) {
            return milliseconds + "ms";
        }
        long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
        long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;
        long millis = milliseconds % 1000;
        StringBuilder sb = new StringBuilder();
        if (hours > 0) {
            sb.append(hours).append("h");
        }
        if (minutes > 0) {
            sb.append(minutes).append("m");
        }
        if (seconds > 0 || (hours == 0 && minutes == 0)) {
            sb.append(seconds).append("s");
        }
        if (millis > 0 || (hours == 0 && minutes == 0 && seconds == 0)) {
            sb.append(millis).append("ms");
        }
        return sb.toString();
    }

    /**
     * 将毫秒转化成 1h2m3s4ms 格式
     *
     * @param milliseconds
     * @return
     */
    public static String humanDuration(long milliseconds) {
        if (milliseconds < 10000) {
            return milliseconds + "ms";
        }
        long hours = TimeUnit.MILLISECONDS.toHours(milliseconds);
        long minutes = TimeUnit.MILLISECONDS.toMinutes(milliseconds) % 60;
        long seconds = TimeUnit.MILLISECONDS.toSeconds(milliseconds) % 60;
        StringBuilder sb = new StringBuilder();
        if (hours > 0) {
            sb.append(hours).append("h");
        }
        if (minutes > 0) {
            sb.append(minutes).append("m");
        }
        if (seconds > 0 || (hours == 0 && minutes == 0)) {
            sb.append(seconds).append("s");
        }
        return sb.toString();
    }


    /**
     * 持续时长: 1天2小时3分钟
     *
     * @param seconds
     * @return
     */
    public static String humanSeconds(long seconds) {
        long hours = seconds / 3600;
        long minutes = (seconds % 3600) / 60;
        long days = hours / 24;
        if (days > 0) {
            hours = hours % 24;
        }
        StringBuilder sb = new StringBuilder();
        if (days > 0) {
            sb.append(days).append("天");
        }
        if (hours > 0) {
            sb.append(hours).append("时");
        }
        if (minutes > 0) {
            sb.append(minutes).append("分");
        }
        return sb.toString();
    }


    /**
     * Attempts to parse a timestamp from a given string
     * Formats accepted are:
     * <ul>
     * <li>Relative: {@code 5m-ago}, {@code 1h-ago}, etc. See
     * {@link #parseDuration}</li>
     * <li>Absolute human readable dates:
     * <ul><li>"yyyy/MM/dd-HH:mm:ss"</li>
     * <li>"yyyy/MM/dd HH:mm:ss"</li>
     * <li>"yyyy/MM/dd-HH:mm"</li>
     * <li>"yyyy/MM/dd HH:mm"</li>
     * <li>"yyyy-MM-dd"</li></ul></li>
     * <li>Unix Timestamp in seconds or milliseconds:
     * <ul><li>1355961600</li>
     * <li>1355961600000</li>
     * <li>1355961600.000</li></ul></li>
     * </ul>
     *
     * @param datetime The string to parse a value for
     * @return A Unix epoch timestamp in milliseconds
     * @throws IllegalArgumentException if the request was malformed
     */
    public static long parseDateTimeString(final String datetime) {
        if (datetime == null || datetime.isEmpty()) {
            return -1;
        }
        if ("now".equalsIgnoreCase(datetime)) {
            return System.currentTimeMillis();
        }
        if (datetime.toLowerCase().endsWith("-ago")) {
            long interval = parseDuration(datetime.substring(0, datetime.length() - 4));
            return System.currentTimeMillis() - interval;
        }
        if (datetime.contains("/")) {
            return parseDateTime(datetime);
        } else if (datetime.contains("-")) {
            return parseDateTime2(datetime);
        } else {
            return parseDateTime3(datetime);
        }
    }


    /**
     * Parses a human-readable duration (e.g, "10m", "3h", "14d") into seconds.
     * <p>
     * Formats supported:<ul>
     * <li>{@code ms}: milliseconds</li>
     * <li>{@code s}: seconds</li>
     * <li>{@code m}: minutes</li>
     * <li>{@code h}: hours</li>
     * <li>{@code d}: days</li>
     * <li>{@code w}: weeks</li>
     * <li>{@code n}: month (30 days)</li>
     * <li>{@code y}: years (365 days)</li></ul>
     *
     * @param duration The human-readable duration to parse.
     * @return A strictly positive number of milliseconds.
     * @throws IllegalArgumentException if the interval was malformed.
     */
    public static final long parseDuration(final String duration) {
        long interval;
        long multiplier;
        double temp;
        int unit = 0;
        while (Character.isDigit(duration.charAt(unit))) {
            unit++;
            if (unit >= duration.length()) {
                throw new IllegalArgumentException("Invalid duration, must have an "
                        + "integer and unit: " + duration);
            }
        }
        try {
            interval = Long.parseLong(duration.substring(0, unit));
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid duration (number): " + duration);
        }
        if (interval <= 0) {
            throw new IllegalArgumentException("Zero or negative duration: " + duration);
        }
        switch (duration.toLowerCase().charAt(duration.length() - 1)) {
            case 's':
                if (duration.charAt(duration.length() - 2) == 'm') {
                    return interval;
                }
                multiplier = 1;
                break;                        // seconds
            case 'm':
                multiplier = 60;
                break;               // minutes
            case 'h':
                multiplier = 3600;
                break;             // hours
            case 'd':
                multiplier = 3600 * 24;
                break;        // days
            case 'w':
                multiplier = 3600 * 24 * 7;
                break;    // weeks
            case 'n':
                multiplier = 3600 * 24 * 30;
                break;   // month (average)
            case 'y':
                multiplier = 3600 * 24 * 365;
                break;  // years (screw leap years)
            default:
                throw new IllegalArgumentException("Invalid duration (suffix): " + duration);
        }
        multiplier *= 1000;
        temp = (double) interval * multiplier;
        if (temp > Long.MAX_VALUE) {
            throw new IllegalArgumentException("Duration must be < Long.MAX_VALUE ms: " + duration);
        }
        return interval * multiplier;
    }


    /**
     * 解析yyyy/MM/dd格式的时间
     *
     * @param datetime
     * @return
     */
    private static long parseDateTime(String datetime) {
        try {
            SimpleDateFormat fmt = null;
            switch (datetime.length()) {
                case 10:
                    fmt = new SimpleDateFormat("yyyy/MM/dd");
                    break;
                case 16:
                    fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm");
                    break;
                case 19:
                    fmt = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss");
                    break;
                default:
                    throw new IllegalArgumentException("Invalid absolute date: " + datetime);
            }
            return fmt.parse(datetime).getTime();
        } catch (ParseException e) {
            throw new IllegalArgumentException("Invalid date: " + datetime
                    + ". " + e.getMessage());
        }
    }

    /**
     * 解析yyyy-MM-dd格式的时间
     *
     * @param datetime
     * @return
     */
    private static long parseDateTime2(String datetime) {
        try {
            SimpleDateFormat fmt = null;
            switch (datetime.length()) {
                case 10:
                    fmt = new SimpleDateFormat("yyyy-MM-dd");
                    break;
                case 16:
                    fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm");
                    break;
                case 19:
                    fmt = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                    break;
                default:
                    throw new IllegalArgumentException("Invalid absolute date: " + datetime);
            }
            return fmt.parse(datetime).getTime();
        } catch (ParseException e) {
            throw new IllegalArgumentException("Invalid date: " + datetime
                    + ". " + e.getMessage());
        }
    }


    /**
     * 解析时间戳，如：
     * Unix Timestamp in seconds or milliseconds:
     * <ul>
     *     <li>1355961600</li>
     *     <li>1355961600000</li>
     *     <li>1355961600.000</li>
     * </ul>
     *
     * @param datetime
     * @return
     */
    private static long parseDateTime3(String datetime) {
        try {
            long time;
            final boolean contains_dot = datetime.contains(".");
            // [0-9]{10} ten digits
            // \\. a dot
            // [0-9]{1,3} one to three digits
            final boolean valid_dotted_ms = datetime.matches("^[0-9]{10}\\.[0-9]{1,3}$");
            if (contains_dot) {
                if (!valid_dotted_ms) {
                    throw new IllegalArgumentException("Invalid time: " + datetime
                            + ". Millisecond timestamps must be in the format "
                            + "<seconds>.<ms> where the milliseconds are limited to 3 digits");
                }
                time = Long.parseLong(datetime.replace(".", ""));
            } else {
                time = Long.parseLong(datetime);
            }
            if (time < 0) {
                throw new IllegalArgumentException("Invalid time: " + datetime
                        + ". Negative timestamps are not supported.");
            }
            // this is a nasty hack to determine if the incoming request is
            // in seconds or milliseconds. This will work until November 2286
            if (datetime.length() <= 10) {
                time *= 1000;
            }
            return time;
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid time: " + datetime
                    + ". " + e.getMessage());
        }
    }


}
